#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

#define MAXFILE     32768

char *modestr(mode_t mode, char *modestr)
{
    strcpy(modestr, "----------");

    if (S_ISREG(mode)) modestr[0] = '-';
    if (S_ISDIR(mode)) modestr[0] = 'd';
    if (S_ISCHR(mode)) modestr[0] = 'c';
    if (S_ISBLK(mode)) modestr[0] = 'b';
    if (S_ISLNK(mode)) modestr[0] = 'l';
    if (S_ISFIFO(mode)) modestr[0] = 'f';
    if (S_ISSOCK(mode)) modestr[0] = 's';

    if (mode & S_IRUSR) modestr[1] = 'r';
    if (mode & S_IWUSR) modestr[2] = 'w'; 
    if (mode & S_IXUSR) modestr[3] = 'x';
    if (mode & S_IRGRP) modestr[4] = 'r';
    if (mode & S_IWGRP) modestr[5] = 'w'; 
    if (mode & S_IXGRP) modestr[6] = 'x';
    if (mode & S_IROTH) modestr[7] = 'r';
    if (mode & S_IWOTH) modestr[8] = 'w'; 
    if (mode & S_IXOTH) modestr[9] = 'x';
    if (mode & S_ISUID) modestr[3] = 's';
    if (mode & S_ISGID) modestr[6] = 's';
    if (mode & S_ISVTX) modestr[9] = 't';

    return modestr;
}

void ls_file(char *filename, struct stat *st, char *str, int list)
{
    char mode[11];
    char time[17];
    struct passwd *pw;
    struct group *gr;

    if (list)
    {
        if (str == NULL)
            str = "%s %d %s %s %ld %s %s\n";
        pw = getpwuid(st->st_uid);
        gr = getgrgid(st->st_gid);
        strftime(time, 17, "%F %R", localtime(&st->st_mtime));
        printf(str, modestr(st->st_mode, mode), st->st_nlink, pw->pw_name, 
            gr->gr_name, st->st_size, time, filename);
    }
    else
        printf("%s\n", filename);
}

int getwidth(int i)
{
    int n = 1;
    while((i=i/10) > 0) n++;

    return n;
}

void ls_dir(char *filename, int all, int list)
{
    DIR *dp = opendir(filename);
    struct dirent *dirp;
    int maxlinklen, maxnamelen, maxgrplen, maxsizelen, count;
    int linklen, namelen, grplen, sizelen, i;
    struct stat st[MAXFILE];
    char *dnames[MAXFILE];
    char string[50];
    
    if (strcmp(filename, ".") && strcmp(filename, "./"))
        chdir(filename);

    if (!list)
    {
        while ((dirp = readdir(dp)) != NULL)
        {
            if (!all)
            {
                if (!strcmp(dirp->d_name, ".") || 
                    !strcmp(dirp->d_name, ".."))
                    continue;
            }
            
            ls_file(dirp->d_name, NULL, NULL, list);
        }
    }
    else
    {
        maxlinklen = maxnamelen = maxgrplen = maxsizelen = count = 0;
        while ((dirp = readdir(dp)) != NULL)
        {
            if (!all)
            {
                if (!strcmp(dirp->d_name, ".") || 
                    !strcmp(dirp->d_name, ".."))
                    continue;
            }

            if (lstat(dirp->d_name, &st[count]) < 0)
            {
                perror("lstat error");
                exit(0);
            }

            dnames[count] = dirp->d_name;

            if ((linklen = getwidth(st[count].st_nlink)) > maxlinklen)
                maxlinklen = linklen;
            if ((namelen = strlen(getpwuid(st[count].st_uid)->pw_name))
                > maxnamelen)
                maxnamelen = namelen;
            if ((grplen = strlen(getgrgid(st[count].st_gid)->gr_name))
                > maxgrplen)
                maxgrplen = grplen;
            if ((sizelen = getwidth(st[count].st_size)) > maxsizelen)
                maxsizelen = sizelen;

            count++;
        }

        printf("total %d\n", count);
        sprintf(string, "%%s %%%dd %%-%ds %%-%ds %%%dld %%s %%s\n", 
            maxlinklen, maxnamelen, maxgrplen, maxsizelen);

        for (i = 0; i < count; i++)
            ls_file(dnames[i], &st[i], string, list);
    }

    if (strcmp(filename, ".") && strcmp(filename, "./"))
        chdir(".");
}

int main(int argc, char *argv[])
{
    int list, all, i, j;
    char **filename;
    struct stat st;

    list = 0;
    all = 0;
    if (argc == 1)
    {
        filename = malloc(2 * sizeof(char *));
        filename[0] = ".";
        filename[1] = NULL;
        j = 1;
    }
    else
    {
        filename = malloc(argc * sizeof(char *));
        for (i = 1, j = 0; argv[i] != NULL; i ++)
        {
            if ((argv[i][0] != '-')) 
                filename[j++] = argv[i];
            else if (!strcmp(argv[i]+1, "l"))
                list = 1;
            else if (!strcmp(argv[i]+1, "a"))
                all = 1;
            else if (!strcmp(argv[i]+1, "al") || 
                !strcmp(argv[i]+1, "la"))
                list = all = 1;
            else
            {
                printf("usage: %s <-l> <-a> <filename...>\n",
                    argv[0]);
                exit(0);
            }
        }
        if (j == 0)
            filename[j++] = ".";
        filename[j] = NULL;
    }

    for (i = 0; filename[i]; i ++)
    {
        lstat(filename[i], &st);
        if (S_ISDIR(st.st_mode))
        {   
            if (j > 1)
                printf("%s:\n", filename[i]);
            ls_dir(filename[i], all, list);
            if (j > 1)
                printf("\n");
        }
        else if (list)
            ls_file(filename[i], &st, NULL, list);
        else
            ls_file(filename[i], NULL, NULL, list);
    }

    free(filename);

    return 0;
}
